'use client'; import { useState, useEffect, useRef } from 'react'; import { useRouter, useParams } from 'next/navigation'; import Link from 'next/link'; import { fetchApi } from '@/lib/utils/client'; import { useStudioContext } from '@/app/studio/context'; import { useAlertConfigContext } from '../../context'; import { Separator } from '@/components/ui/separator'; import AlertPreviewPanel from '../../_components/AlertPreviewPanel'; import AlertFormPanel from '../../_components/AlertFormPanel'; import { createEmptyForm } from '../../types'; import type { FormState, PendingFiles } from '../../types'; import type { AlertConfigItem } from '@/types/response/donation/alertConfig'; export default function AlertEditPage() { const router = useRouter(); const { id } = useParams<{ id: string }>(); const numericId = parseInt(id); const { channelID, memberID } = useStudioContext(); const { items, widgetToken, loading, setSaving } = useAlertConfigContext(); const [editingItem, setEditingItem] = useState(null); const [form, setForm] = useState(createEmptyForm()); const [formInitialized, setFormInitialized] = useState(false); const [pendingFiles, setPendingFiles] = useState({ image: null, sound: null }); const [localSaving, setLocalSaving] = useState(false); const iframeRef = useRef(null); const formRef = useRef(form); formRef.current = form; // ── items 로드 후 form 초기화 ──────────────────── useEffect(() => { if (formInitialized || items.length === 0) { return; } const found = items.find(item => item.id === numericId); if (found) { setEditingItem(found); const { id, ...rest } = found; void id; setForm(rest); setFormInitialized(true); } else if (!loading) { // 준비 완료인데 못 찾음 alert('알림 설정을 찾을 수 없습니다.'); router.push('/studio/donation/alert/list'); } }, [items, loading, numericId, formInitialized, router]); // ── blob URL cleanup ───────────────────────────── const cleanupBlobUrls = (f: FormState) => { if (f.imageUrl?.startsWith('blob:')) { URL.revokeObjectURL(f.imageUrl); } if (f.soundUrl?.startsWith('blob:')) { URL.revokeObjectURL(f.soundUrl); } }; useEffect(() => { return () => { cleanupBlobUrls(formRef.current); }; }, []); // ── 폼 → iframe 미리보기 동기화 ───────────────── useEffect(() => { if (!iframeRef.current?.contentWindow) { return; } iframeRef.current.contentWindow.postMessage({ type: 'ALERT_PREVIEW', config: form, }, window.location.origin); }, [form]); // ── 폼 필드 변경 ──────────────────────────────── const handleFormChange = (field: K, value: FormState[K]) => { setForm(prev => { if ((field === 'imageUrl' || field === 'soundUrl') && typeof prev[field] === 'string' && (prev[field] as string).startsWith('blob:')) { URL.revokeObjectURL(prev[field] as string); } return { ...prev, [field]: value }; }); if (field === 'imageUrl' && value === null) { setPendingFiles(prev => ({ ...prev, image: null })); } if (field === 'soundUrl' && value === null) { setPendingFiles(prev => ({ ...prev, sound: null })); } }; // ── 파일 업로드 헬퍼 ───────────────────────────── const uploadFile = async (file: File, type: 'image'|'sound'): Promise => { const formData = new FormData(); formData.append('file', file); formData.append('type', type); formData.append('channelID', channelID!.toString()); const res = await fetchApi<{ url: string }>('/api/studio/donation/alert/config/upload', { method: 'POST', body: formData, }); return res.data?.url ?? ''; }; // ── 저장 ───────────────────────────────────────── const handleSave = async () => { if (!channelID || !editingItem) { return; } if (!form.message.trim()) { alert('메시지를 입력해 주세요.'); return; } if (form.amount < 1) { alert('금액은 1원 이상이어야 합니다.'); return; } if (form.displayDurationSec < 1) { alert('노출 시간은 1초 이상이어야 합니다.'); return; } setLocalSaving(true); setSaving(true); try { let finalImageUrl = form.imageUrl; let finalSoundUrl = form.soundUrl; if (pendingFiles.image) { finalImageUrl = await uploadFile(pendingFiles.image, 'image'); } if (pendingFiles.sound) { finalSoundUrl = await uploadFile(pendingFiles.sound, 'sound'); } const item = { id: editingItem.id, ...form, imageUrl: finalImageUrl, soundUrl: finalSoundUrl, popupEffect: form.popupEffect || null, textEffect: form.textEffect || null, nicknameFontFamily: form.nicknameFontFamily || null, amountFontFamily: form.amountFontFamily || null, messageFontFamily: form.messageFontFamily || null, }; await fetchApi('/api/studio/donation/alert/config/batch', { method: 'POST', body: { channelID, memberID, items: [item], deleteIDs: [] }, }); cleanupBlobUrls(form); alert('수정되었습니다.'); } catch (err) { alert(err instanceof Error ? err.message : '저장에 실패했습니다.'); } finally { setLocalSaving(false); setSaving(false); } }; // ── 취소 ───────────────────────────────────────── const handleCancel = () => { cleanupBlobUrls(form); router.push('/studio/donation/alert/list'); }; // ── 로딩 중 ────────────────────────────────────── if (!formInitialized) { return
준비 중...
; } return ( <>

후원 알림 수정

< 목록으로
{ const previewUrl = URL.createObjectURL(file); if (type === 'image') { setPendingFiles(prev => ({ ...prev, image: file })); handleFormChange('imageUrl', previewUrl); } else { setPendingFiles(prev => ({ ...prev, sound: file })); handleFormChange('soundUrl', previewUrl); } }} onFormChange={handleFormChange} onSave={handleSave} onCancel={handleCancel} />
); }